﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace PlutoStudio.Data.Common
{
    public class PropertyItem
    {
        public ColumnAttribute Attribute { get; private set; }

        internal PropertyInfo Property { get; private set; }

        public string PropertyName
        {
            get
            {
                return Property.Name;
            }
        }

        public string ColumnName
        {
            get
            {
                var result = Attribute.ColumnName;
                if (string.IsNullOrWhiteSpace(result))
                {
                    result = PropertyName;
                }
                return result;
            }
        }

        public Type PropertyType
        {
            get { return Property.PropertyType; }
        }

        public object GetValue(object item)
        {
            return Property.GetValue(item, null);
        }

        public void SetValue(object item, object value)
        {
            Property.SetValue(item, value, null);
        }

        internal PropertyItem()
        {

        }

        internal PropertyItem(PropertyInfo property, ColumnAttribute attribute)
        {
            // TODO: Complete member initialization
            this.Property = property;
            this.Attribute = attribute;
        }
    }

    internal static class PropertyFactory
    {
        private static readonly ConcurrentDictionary<Type, TableAttribute> CachedTableName;
        private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyItem>> cachedPropertyItems;

        static PropertyFactory()
        {
            CachedTableName = new ConcurrentDictionary<Type, TableAttribute>();
            cachedPropertyItems = new ConcurrentDictionary<Type, Dictionary<string, PropertyItem>>();
        }

        public static PropertyItem GetProperty(Type type, string column)
        {
            return GetProperties(type)[column];
        }

        public static PropertyItem GetProperty<T>(string column)
        {
            return GetProperty(typeof(T), column);
        }

        public static PropertyItem GetProperty(this object item, string column)
        {
            return GetProperty(item.GetType(), column);
        }

        public static IEnumerable<PropertyItem> GetPrimaryKeys(Type type)
        {
            var properies = GetProperties(type).Values;
            return properies.Where(p => p.Attribute.IsPrimaryKey);
        }

        public static Dictionary<string, PropertyItem> GetProperties(Type type)
        {
            if (!cachedPropertyItems.ContainsKey(type))
            {
                cachedPropertyItems.TryAdd(type, GeneratePropertyItems(type));
            }
            return cachedPropertyItems[type];
        }

        public static string GetTableName(this object entity)
        {
            var type=entity.GetType();
            var attr = GetTableAttribute(type);
            var name = attr.TableName;
            if (attr.SplitTable)
            {
                name = attr.TableNameSelector(entity);
            }
            if (string.IsNullOrWhiteSpace(name))
            {
                name = type.Name;
            }
            return name;
        }

        public static string GetTableName(this Type type)
        {
            var attr = GetTableAttribute(type);
            var name = attr.TableName;
            if (string.IsNullOrWhiteSpace(name))
            {
                name = type.Name;
            }
            return name;

        }

        public static int GetDatabaseId(this object entity)
        {
            return GetTableAttribute(entity.GetType()).GetDatabaseId(entity);
        }

        public static int GetDatabaseId(this Type type)
        {
            return GetTableAttribute(type).GetDatabaseId();
        }


        public static TableAttribute GetTableAttribute(Type type)
        {
            if (!CachedTableName.ContainsKey(type))
            {
                CachedTableName.TryAdd(type, GenerateTableAttribute(type));
            }
            return CachedTableName[type] as TableAttribute;
        }

        private static TableAttribute GenerateTableAttribute(Type type)
        {
            var attribute = type.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault() as TableAttribute;
            return attribute;
        }

        private static Dictionary<string, PropertyItem> GeneratePropertyItems(Type type)
        {
            var properties = from property in type.GetProperties()
                             let attribute = property.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() as ColumnAttribute
                             where attribute != null
                             select new { key = property.Name, value = new PropertyItem(property, attribute) };
            return properties.ToDictionary(p => p.key, p => p.value);
        }
    }
}
